/*******************************************************************************
 * copyright (c) 将狼才鲸
 *
 * @file    mp4mux.c
 * @brief   将已编码的音频视频包封装成mp4容器文件
 ******************************************************************************/

/*================================= 头文件 ====================================*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

/*================================= 宏定义 ====================================*/
#define MODE_MP4  0x01
#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
#define AV_NOPTS_VALUE          ((int64_t)UINT64_C(0x8000000000000000))
#define MOV_TIMESCALE 1000
#define AV_INPUT_BUFFER_PADDING_SIZE 64
#define av_malloc malloc
#define av_free free

#define AV_CH_FRONT_LEFT             0x00000001
#define AV_CH_FRONT_RIGHT            0x00000002
#define AV_CH_FRONT_CENTER           0x00000004
#define AV_CH_LAYOUT_MONO              (AV_CH_FRONT_CENTER)
#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)

#define FF_MOV_FLAG_FRAGMENT              (1 <<  1)
#define FF_MOV_FLAG_EMPTY_MOOV            (1 <<  2)

#define AVERROR(e) (-(e))   ///< Returns a negative error code from a POSIX error code, to return from library functions.
#define AVUNERROR(e) (-(e)) ///< Returns a POSIX error code from a library function error return value.

#ifndef AV_RB24
#   define AV_RB24(x)                           \
    ((((const uint8_t*)(x))[0] << 16) |         \
     (((const uint8_t*)(x))[1] <<  8) |         \
      ((const uint8_t*)(x))[2])
#endif
#ifndef AV_RB32
#   define AV_RB32(x)                                \
    (((uint32_t)((const uint8_t*)(x))[0] << 24) |    \
               (((const uint8_t*)(x))[1] << 16) |    \
               (((const uint8_t*)(x))[2] <<  8) |    \
                ((const uint8_t*)(x))[3])
#endif

/*================================ 数据类型 ===================================*/
enum AVCodecID {
    AV_CODEC_ID_NONE,

    /* video codecs */
    AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
    AV_CODEC_ID_MPEG4,
    AV_CODEC_ID_RAWVIDEO,
    AV_CODEC_ID_H264,
    AV_CODEC_ID_HEVC,
#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC

    /* audio codecs */
    AV_CODEC_ID_MP2 = 0x15000,
    AV_CODEC_ID_MP3, ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
    AV_CODEC_ID_AAC,
    AV_CODEC_ID_AC3,
    AV_CODEC_ID_AAC_LATM,
    AV_CODEC_ID_MPEG2TS = 0x20000, /**< _FAKE_ codec to indicate a raw MPEG-2 TS
                                * stream (only used by libavformat) */
};

enum AVMediaType {
    AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA
    AVMEDIA_TYPE_VIDEO,
    AVMEDIA_TYPE_AUDIO,
    AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous
    AVMEDIA_TYPE_SUBTITLE,
    AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse
    AVMEDIA_TYPE_NB
};

typedef struct AVCodecTag {
    enum AVCodecID id;
    unsigned int tag;
} AVCodecTag;

typedef struct AVCodecParameters {
    uint32_t         codec_tag; // 4字节字符的编码器缩写
    enum AVCodecID   codec_id;
    enum AVMediaType codec_type;
    int format; // 像素格式或音频采样格式
    int      sample_rate; // 音频采样率
    int      channels; // 通道数
    uint64_t channel_layout;
    int width;
    int height;
} AVCodecParameters;

typedef struct AVRational{
    int num; ///< Numerator
    int den; ///< Denominator
} AVRational;

typedef struct AVStream {
    AVCodecParameters *codecpar;
    AVRational time_base;
} AVStream;

typedef struct AVOutputFormat {
    int a;
} AVOutputFormat;

typedef struct AVIOContext {
    int a;
} AVIOContext;

typedef struct AVFormatContext {
    char *name_suffix;      // 文件名后缀
    void *mp4mux_context;   // MOVMuxContext
    struct AVOutputFormat *oformat;
    int     nb_streams;
    unsigned int nb_chapters;
    AVStream **streams;
    int max_streams;
    AVIOContext *pb;
} AVFormatContext;

typedef struct MOVTrack {
    AVStream        *st;
    int         language;
    int         mode;
    int         tag; ///< stsd fourcc
    AVCodecParameters *par;
    int64_t     start_dts;
    int64_t     start_cts;
    int64_t     end_pts;
    int         end_reliable;
    int64_t     dts_shift;
    unsigned    timescale;
    int         height;
    int         vos_len;
    uint8_t     *vos_data;
    int         entry;
    long        sample_count;   //'moov''trak''mdia''minf''stbl''stsc'
    long        sample_size;    //'moov''trak''mdia''minf''stbl''stsz'
    long        chunkCount;     //'moov''trak''mdia''minf''stbl''stsc'
} MOVTrack;

typedef struct MOVMuxContext {
    int mode;               // MODE_MP4
    AVFormatContext *fc;
    int     nb_streams;
    int     chapter_track;
    MOVTrack *tracks;
    int video_track_timescale;
    int flags;
    int moov_written;
    int64_t mdat_pos;
    uint64_t mdat_size;
} MOVMuxContext;

typedef struct AVPacket {
    char *pkt_data;
    char *pkt_len;
    int64_t dts;
    uint8_t *data;
    int   size;
    int   stream_index;
    int64_t pts;
    // AVPacketSideData *side_data;
    int side_data_elems;
    int64_t duration;
    int64_t pos;                            ///< byte position in stream, -1 if unknown
} AVPacket;

/*================================ 变量定义 ===================================*/
static int ff_mov_iso639_to_lang(const char lang[4], int mp4);
static int mov_get_h264_codec_tag(AVFormatContext *s, MOVTrack *track);
static unsigned int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track);
static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track);
static int av_get_bits_per_sample(enum AVCodecID codec_id);
static int av_get_exact_bits_per_sample(enum AVCodecID codec_id);
//--static int mov_flush_fragment(AVFormatContext *s, int force);

const AVCodecTag ff_codec_movvideo_tags[] = {
    { AV_CODEC_ID_MPEG4, MKTAG('m', 'p', '4', 'v') },
    { AV_CODEC_ID_MPEG4, MKTAG('D', 'I', 'V', 'X') }, /* OpenDiVX *//* sample files at http://heroinewarrior.com/xmovie.php3 use this tag */
    { AV_CODEC_ID_MPEG4, MKTAG('X', 'V', 'I', 'D') },

    { AV_CODEC_ID_HEVC, MKTAG('h', 'e', 'v', '1') }, /* HEVC/H.265 which indicates parameter sets may be in ES */
    { AV_CODEC_ID_HEVC, MKTAG('h', 'v', 'c', '1') }, /* HEVC/H.265 which indicates parameter sets shall not be in ES */
    { AV_CODEC_ID_HEVC, MKTAG('d', 'v', 'h', 'e') }, /* HEVC-based Dolby Vision derived from hev1 */
                                                     /* dvh1 is handled within mov.c */

    { AV_CODEC_ID_H264, MKTAG('a', 'v', 'c', '1') }, /* AVC-1/H.264 */
    { AV_CODEC_ID_H264, MKTAG('a', 'v', 'c', '2') },
    { AV_CODEC_ID_H264, MKTAG('a', 'v', 'c', '3') },
    { AV_CODEC_ID_H264, MKTAG('a', 'v', 'c', '4') },

    { AV_CODEC_ID_NONE, 0 },
};

const AVCodecTag ff_codec_movaudio_tags[] = {
    { AV_CODEC_ID_AAC,             MKTAG('m', 'p', '4', 'a') },
    { AV_CODEC_ID_AC3,             MKTAG('a', 'c', '-', '3') }, /* ETSI TS 102 366 Annex F */
    { AV_CODEC_ID_AC3,             MKTAG('s', 'a', 'c', '3') }, /* Nero Recode */

    { AV_CODEC_ID_MP2,             MKTAG('.', 'm', 'p', '2') },
    { AV_CODEC_ID_MP3,             MKTAG('.', 'm', 'p', '3') },
    { AV_CODEC_ID_MP3,             MKTAG('m', 'p', '3', ' ') }, /* vlc */
    { AV_CODEC_ID_MP3,             0x6D730055                },

    { AV_CODEC_ID_NONE, 0 },
};

/*================================ 接口函数 ===================================*/
int mp4mux_init(AVFormatContext *s)
{
    MOVMuxContext *mov = s->mp4mux_context;
    mov->fc = s;
    mov->mode = MODE_MP4;
    mov->nb_streams = s->nb_streams;
    mov->chapter_track = mov->nb_streams++;

    for (int i = 0; i < s->nb_streams; i++) {
        AVStream *st= s->streams[i];
        MOVTrack *track= &mov->tracks[i];

        track->st  = st;
        track->language = ff_mov_iso639_to_lang("und", 0);
        track->mode = mov->mode;
        track->tag  = mov_find_codec_tag(s, track);
        track->start_dts  = AV_NOPTS_VALUE;
        track->start_cts  = AV_NOPTS_VALUE;
        track->end_pts    = AV_NOPTS_VALUE;
        track->dts_shift  = AV_NOPTS_VALUE;
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            if (mov->video_track_timescale) {
                track->timescale = mov->video_track_timescale;
            } else {
                track->timescale = st->time_base.den;
                while(track->timescale < 10000)
                    track->timescale *= 2;
            }
        } else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            track->timescale = st->codecpar->sample_rate;
            track->sample_size = (av_get_bits_per_sample(st->codecpar->codec_id) >> 3) * st->codecpar->channels;
        } else {
            track->timescale = MOV_TIMESCALE;
        }
        if (!track->height)
            track->height = st->codecpar->height;

    }

    /* 1. open output file */
}

int mp4mux_write_header(AVFormatContext *s)
{
    AVIOContext *pb = s->pb;
    MOVMuxContext *mov = s->mp4mux_context;
    int i, ret, nb_tracks = s->nb_streams;

    if (mov->mode & MODE_MP4 && s->nb_chapters)
        nb_tracks++;

    for (i = 0; i < s->nb_streams; i++) {
        int j;
        AVStream *st= s->streams[i];
        MOVTrack *track= &mov->tracks[i];

        for (j = 0; j < s->nb_streams; j++) {
            AVStream *stj= s->streams[j];
            MOVTrack *trackj= &mov->tracks[j];
            if (j == i)
                continue;

            if (stj->codecpar->codec_type != AVMEDIA_TYPE_AUDIO ||
                trackj->par->channel_layout != AV_CH_LAYOUT_MONO ||
                trackj->language != track->language ||
                trackj->tag != track->tag
            )
                continue;
        }
    }

    //init_output_bsfs
    //av_bsf_init

}

int mp4mux_write_packet(AVFormatContext *s, AVPacket *pkt)
{
    MOVMuxContext *mov = s->mp4mux_context;
    AVIOContext *pb = s->pb;
    MOVTrack *trk = &mov->tracks[pkt->stream_index];
    int size = pkt->size, ret = 0, offset = 0;
    uint8_t *reformatted_data = NULL;

    //init_output_stream
    //init_output_bsfs
    //avformat_write_header
    //av_bsf_send_packet
    //av_interleaved_write_frame
    //write_packets_from_bsfs
    //write_packet

    if (!pkt) {
        //--mov_flush_fragment(s, 1);
        //......
        return 1;
    }

    //mov_write_single_packet
    //return mov_write_single_packet(s, pkt);
    //ff_mov_write_packet

    /* 1. 写aac extradata */
    if (trk->par->codec_id == AV_CODEC_ID_AAC) {
        //TODO:
    }

    if (!pkt->size) {
        return 0;             /* Discard 0 sized packets */
    }

    unsigned int samples_in_chunk = 0;
    if (trk->sample_size)
        samples_in_chunk = size / trk->sample_size;
    else
        samples_in_chunk = 1;

    AVCodecParameters *par = trk->par;

    /* copy extradata if it exists */
    // if (trk->vos_len == 0 && par->extradata_size > 0) {
    //     trk->vos_len  = par->extradata_size;
    //     trk->vos_data = av_malloc(trk->vos_len + AV_INPUT_BUFFER_PADDING_SIZE);
    //     if (!trk->vos_data) {
    //         ret = AVERROR(ENOMEM);
    //         goto err;
    //     }
    //     memcpy(trk->vos_data, par->extradata, trk->vos_len);
    //     memset(trk->vos_data + trk->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
    // }

    /* 2. 拷贝数据 */
    if ((par->codec_id == AV_CODEC_ID_H264 ||
         par->codec_id == AV_CODEC_ID_HEVC ||
         par->codec_id == AV_CODEC_ID_AC3) && !trk->vos_len) {
        /* copy frame to create needed atoms */
        trk->vos_len  = size;
        trk->vos_data = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
        if (!trk->vos_data) {
            ret = AVERROR(ENOMEM);
            goto err;
        }
        // 将包数据写入文件，还是新分配的内存
        // ???vos_data其实是写文件内容时下一次要写的所有数据？？？这里应该用不上
        // memcpy(trk->vos_data, pkt->data, size);
        // memset(trk->vos_data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
        //TODO: 写入到文件中
        //TODO: avio_write(pb, track->vos_data, track->vos_len);
        //mov_auto_flush_fragment
        //--mov_flush_fragment 流式文件才用到

    }

    //aac非法判断 aac_adtstoasc

    // 整个包直接写到文件中去
    /* from x264 or from bytestream H.264 */
    /* NAL reformatting needed */
    //TODO:
    //size = ff_avc_parse_nal_units(pb, pkt->data, pkt->size);
    if (par->codec_id == AV_CODEC_ID_H264 && trk->vos_len > 0 && *(uint8_t *)trk->vos_data != 1) {
        /* from x264 or from bytestream H.264 */
        /* NAL reformatting needed */
        // size = ff_avc_parse_nal_units(pb, pkt->data, pkt->size);
    } else if (par->codec_id == AV_CODEC_ID_HEVC && trk->vos_len > 6 &&
               (AV_RB24(trk->vos_data) == 1 || AV_RB32(trk->vos_data) == 1)) {
        /* extradata is Annex B, assume the bitstream is too and convert it */
        // size = ff_hevc_annexb2mp4(pb, pkt->data, pkt->size, 0, NULL);
    } else {
        // avio_write(pb, pkt->data, size);
    }

    //TODO: 计算时间戳
    //赋值很多trk参数

    /* 3. 索引值记录 */
    trk->entry++;
    trk->sample_count += samples_in_chunk;

    mov->mdat_size    += size;

err:

    if (pkt->data != reformatted_data)
        av_free(reformatted_data);
    return ret;
}

int mp4mux_write_trailer(AVFormatContext *s)
{

}

void mp4mux_free(AVFormatContext *s)
{

}

/*============================== 模块内部函数 =================================*/

/* map numeric codes from mdhd atom to ISO 639 */
/* cf. QTFileFormat.pdf p253, qtff.pdf p205 */
/* http://developer.apple.com/documentation/mac/Text/Text-368.html */
/* deprecated by putting the code as 3*5 bits ASCII */
static const char mov_mdhd_language_map[][4] = {
    /* 0-9 */
    "eng", "fra", "ger", "ita", "dut", "sve", "spa", "dan", "por", "nor",
    "heb", "jpn", "ara", "fin", "gre", "ice", "mlt", "tur", "hr "/*scr*/, "chi"/*ace?*/,
    "urd", "hin", "tha", "kor", "lit", "pol", "hun", "est", "lav",    "",
    "fo ",    "", "rus", "chi",    "", "iri", "alb", "ron", "ces", "slk",
    "slv", "yid", "sr ", "mac", "bul", "ukr", "bel", "uzb", "kaz", "aze",
    /*?*/
    "aze", "arm", "geo", "mol", "kir", "tgk", "tuk", "mon",    "", "pus",
    "kur", "kas", "snd", "tib", "nep", "san", "mar", "ben", "asm", "guj",
    "pa ", "ori", "mal", "kan", "tam", "tel",    "", "bur", "khm", "lao",
    /*                   roman? arabic? */
    "vie", "ind", "tgl", "may", "may", "amh", "tir", "orm", "som", "swa",
    /*==rundi?*/
       "", "run",    "", "mlg", "epo",    "",    "",    "",    "",    "",
    /* 100 */
       "",    "",    "",    "",    "",    "",    "",    "",    "",    "",
       "",    "",    "",    "",    "",    "",    "",    "",    "",    "",
       "",    "",    "",    "",    "",    "",    "",    "", "wel", "baq",
    "cat", "lat", "que", "grn", "aym", "tat", "uig", "dzo", "jav"
};

static int ff_mov_iso639_to_lang(const char lang[4], int mp4)
{
    int i, code = 0;

    /* old way, only for QT? */
    for (i = 0; lang[0] && !mp4 && i < FF_ARRAY_ELEMS(mov_mdhd_language_map); i++) {
        if (!strcmp(lang, mov_mdhd_language_map[i]))
            return i;
    }
    /* XXX:can we do that in mov too? */
    if (!mp4)
        return -1;
    /* handle undefined as such */
    if (lang[0] == '\0')
        lang = "und";
    /* 5 bits ASCII */
    for (i = 0; i < 3; i++) {
        uint8_t c = lang[i];
        c -= 0x60;
        if (c > 0x1f)
            return -1;
        code <<= 5;
        code |= c;
    }
    return code;
}

static unsigned int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track)
{
    return mov_get_codec_tag(s, track);
}

unsigned int ff_codec_get_tag(const AVCodecTag *tags, enum AVCodecID id)
{
    while (tags->id != AV_CODEC_ID_NONE) {
        if (tags->id == id)
            return tags->tag;
        tags++;
    }
    return 0;
}

static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track)
{
    unsigned int tag = track->par->codec_tag;

    if (!tag) {
        if (track->par->codec_id == AV_CODEC_ID_H264)
            tag = mov_get_h264_codec_tag(s, track);
        else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) {
            tag = ff_codec_get_tag(ff_codec_movvideo_tags, track->par->codec_id);
        } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) {
            tag = ff_codec_get_tag(ff_codec_movaudio_tags, track->par->codec_id);
        }
    }

    return tag;
}

static int mov_get_h264_codec_tag(AVFormatContext *s, MOVTrack *track)
{
    int tag = track->par->codec_tag;
    AVStream *st = track->st;

    if (!tag)
        tag = MKTAG('a', 'v', 'c', 'i'); //fallback tag

    return tag;
}

static int av_get_bits_per_sample(enum AVCodecID codec_id)
{
    return av_get_exact_bits_per_sample(codec_id);
}

static int av_get_exact_bits_per_sample(enum AVCodecID codec_id)
{
    return 16;
}

// static int mov_flush_fragment(AVFormatContext *s, int force)
// {
//     MOVMuxContext *mov = s->mp4mux_context;
//     int i, first_track = -1;
//     int64_t mdat_size = 0;
//     int ret;
//     int has_video = 0, starts_with_key = 0, first_video_track = 1;

//     if (!(mov->flags & FF_MOV_FLAG_FRAGMENT))
//         return 0;

//     if (!mov->moov_written) {
//         // if ((ret = mov_write_moov_tag(s->pb, mov, s)) < 0)
//         //     return ret;
//         // ffio_wfourcc(s->pb, "mdat");
//         // avio_write(s->pb, buf, buf_size);

//     }
// }

/*============================ 子模块：文件读写 ================================*/

/*============================ 子模块：模块测试 ================================*/
int main (int argc, char *argv[])
{
    printf("test\n");
}

/*================================ 文件结尾 ===================================*/
